
// Copyright (C) 2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

using HtmlAgilityPack;

namespace Derm.BindGen
{
	class DoxygenManualPatch
	{
		public static void PatchMathJaxScripts(string htmlPath)
		{
			HtmlDocument htmlDoc = new HtmlDocument();
			htmlDoc.OptionFixNestedTags = true;
			htmlDoc.OptionAutoCloseOnEnd = true;

			htmlDoc.Load(htmlPath);

			Console.WriteLine("Patching MathJax script at {0}", Path.GetFileName(htmlPath));

			if (htmlDoc.DocumentNode != null) {
				HtmlNodeCollection htmlList;
				HtmlNode briefLink, htmlScriptNode;

				// Correct MathML script parameters
				htmlScriptNode = htmlDoc.DocumentNode.SelectSingleNode("//head/script[@src='../MathML/MathJax.js']");
				if (htmlScriptNode != null)
					htmlScriptNode.SetAttributeValue("src", "MathML/MathJax.js?config=TeX-AMS-MML_HTMLorMML");

				htmlDoc.Save(htmlPath);
			}
		}

		public static void PatchClassMemberPagesLinks(HtmlDocument htmlDoc)
		{
			if (htmlDoc.DocumentNode != null) {
				HtmlNodeCollection htmlList;
				HtmlNode briefLink, htmlScriptNode;

				// Correct MathML script parameters
				htmlList = htmlDoc.DocumentNode.SelectNodes("//a[starts-with(@href,'class_derm_1_1_open_g_l_1_1_gl.html#')]");
				if ((htmlList != null) && (htmlList.Count > 0)) {
					foreach (HtmlNode node in htmlList) {
						string href = node.GetAttributeValue("href", "");

						int htmlDocMemberPathExtIndex = href.IndexOf(".html#");

						string htmlDocMemberPathBase = href.Substring(0, htmlDocMemberPathExtIndex);
						string htmlDocMemberPathHash = href.Substring(htmlDocMemberPathExtIndex+6, href.Length - htmlDocMemberPathExtIndex - 6);
						string htmlDocMemberPath = String.Format("{0}_{1}.html", htmlDocMemberPathBase, htmlDocMemberPathHash);

						href = String.Format("{0}_{1}.html", htmlDocMemberPathBase, htmlDocMemberPathHash);

						// Update link
						node.SetAttributeValue("href", href);
					}
				}
			}
		}

		public static void SeparateClassMemberPages(string htmlPath)
		{
			MemoryStream htmlStreamOriginal;

			MemoryStream htmlStreamSeparatePageModel;
			string htmlStreamSeparatePageModelPath = htmlPath + "_smodel.html";

			if (htmlPath == null)
				throw new ArgumentNullException("htmlPath");
			if (htmlPath.EndsWith("_smodel.html") == true)
				return;

			Console.Write("Separating member pages at {0}", Path.GetFileName(htmlPath));

			// Backup original file appending _smodel.html
			File.Copy(htmlPath, htmlStreamSeparatePageModelPath, true);
			// Load (backup) file into a MemoryStream
			using (FileStream br = new FileStream(htmlStreamSeparatePageModelPath, FileMode.Open, FileAccess.Read)) {
				htmlStreamOriginal = new MemoryStream();
				CopyStream(br, htmlStreamOriginal);
			}

			// Create model for separate member pages
			htmlStreamSeparatePageModel = CreateSeparateClassMemberPageModel(htmlStreamOriginal);
			// Copy to temporary file
			using (FileStream fs = new FileStream(htmlStreamSeparatePageModelPath, FileMode.Create, FileAccess.Write)) {
				htmlStreamSeparatePageModel.Seek(0, SeekOrigin.Begin);
				CopyStream(htmlStreamSeparatePageModel, fs);
			}

			// Load original HTML page
			HtmlDocument htmlDoc = new HtmlDocument();
			htmlDoc.OptionFixNestedTags = true;
			htmlDoc.OptionAutoCloseOnEnd = true;

			htmlStreamOriginal.Seek(0, SeekOrigin.Begin);
			htmlDoc.Load(htmlStreamOriginal);

			if (htmlDoc.DocumentNode != null) {
				HtmlNodeCollection htmlList;
				HtmlNode briefLink, htmlScriptNode;

				// Modify all member declarations
				htmlList = htmlDoc.DocumentNode.SelectNodes("/html/body/div[@class='contents']/table/tr/td/a[@class='el']");
				if ((htmlList != null) && (htmlList.Count > 0)) {
					foreach (HtmlNode htmlNode in htmlList) {
						// Extract anchor identifier
						string docMemberAnchor = htmlNode.GetAttributeValue("href", String.Empty);

						if (docMemberAnchor == String.Empty)
							continue;

						// Derive separate page path from anchor
						int htmlDocMemberPathExtIndex = docMemberAnchor.IndexOf(".html#");

						if (htmlDocMemberPathExtIndex > 0) {
							string htmlDocMemberPathBase = docMemberAnchor.Substring(0, htmlDocMemberPathExtIndex);
							string htmlDocMemberPathHash = docMemberAnchor.Substring(htmlDocMemberPathExtIndex+6, docMemberAnchor.Length - htmlDocMemberPathExtIndex - 6);
							string htmlDocMemberPath = String.Format("{0}_{1}.html", htmlDocMemberPathBase, htmlDocMemberPathHash);

							// Redirect link to member page
							htmlNode.SetAttributeValue("href", htmlDocMemberPath);

							// Remove member documentation from original document
							HtmlNode docMemberAnchorNode = htmlDoc.DocumentNode.SelectSingleNode("/html/body/div[@class='contents']/a[@class='anchor' and @id='"+htmlDocMemberPathHash+"']");

							if (docMemberAnchorNode != null) {
								HtmlNode docMemberNode = docMemberAnchorNode.NextSibling;

								while (docMemberNode.NodeType != HtmlNodeType.Element)
									docMemberNode = docMemberNode.NextSibling;
								if (docMemberNode.Name != "div")
									continue;

								// Add member documentation to separate member document
								HtmlDocument htmlDocMember = new HtmlDocument();
								htmlDocMember.Load(htmlStreamSeparatePageModelPath);

								HtmlNode htmlMemberDiv = htmlDocMember.CreateElement("div");
								htmlMemberDiv.SetAttributeValue("class", "contents");
								htmlMemberDiv.AppendChild(docMemberNode.CloneNode(true));

								HtmlNode htmlMemberBody = htmlDocMember.DocumentNode.SelectSingleNode("//body");
								htmlMemberBody.AppendChild(htmlMemberDiv);

								Console.WriteLine("Save member page {0}", htmlDocMemberPath);

								PatchClassMemberPagesLinks(htmlDocMember);
								htmlDocMember.Save(Path.Combine(Path.GetDirectoryName(htmlPath), htmlDocMemberPath));

								// Remove anchor and text elements
								docMemberAnchorNode = docMemberAnchorNode.ParentNode.RemoveChild(docMemberAnchorNode);
								docMemberNode = docMemberNode.ParentNode.RemoveChild(docMemberNode);
							} else {
								Console.WriteLine("No anchor for {0}", htmlDocMemberPathHash);
							}
						} else {
							Console.WriteLine("Unknown anchor format: {0}", docMemberAnchor);
						}
					}
				}

				PatchClassMemberPagesLinks(htmlDoc);
				htmlDoc.Save(htmlPath);
			}
		}

		private static MemoryStream CreateSeparateClassMemberPageModel(Stream mStream)
		{
			MemoryStream htmlDocStream = new MemoryStream();
			HtmlDocument htmlDoc = new HtmlDocument();
			htmlDoc.OptionFixNestedTags = true;
			htmlDoc.OptionAutoCloseOnEnd = true;

			mStream.Seek(0, SeekOrigin.Begin);
			CopyStream(mStream, htmlDocStream);

			htmlDocStream.Seek(0, SeekOrigin.Begin);
			htmlDoc.Load(htmlDocStream);

			if (htmlDoc.DocumentNode != null) {
				HtmlNodeCollection htmlList;
				HtmlNode htmlNode, htmlBaseNode;

				htmlNode = htmlDoc.DocumentNode.SelectSingleNode("//body/div[@class='contents']");
				if (htmlNode != null) {
					htmlDocStream.SetLength(htmlNode.StreamPosition);
					return (htmlDocStream);
				}
			}

			return (null);
		}

		private static void CopyStream(Stream src, Stream dst)
		{
			byte[] sBuffer = new byte[4096 * 4];
			int sBlockSize = 4096 * 4;

			while (sBlockSize > 0) {
				sBlockSize = src.Read(sBuffer, 0, 4096 * 4);
				if (sBlockSize > 0)
					dst.Write(sBuffer, 0, sBlockSize);
			}
		}
	}
}
